// Copyright 2006 Bret Taylor
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

#include <algorithm>

#include "ast.h"
#include "visitor.h"

using std::for_each;

namespace indie {

// Utility class that lets us Accept all items in a collection with for_each.
class AcceptFunctor {
  public:
    AcceptFunctor(Visitor& visitor) : visitor_(visitor) {
    }

    template<class Type>
    void operator()(Type *ast) const {
        ast->Accept(visitor_);
    }

  private:
    Visitor& visitor_;
};


Ast::Ast(FileLocation* location) :
    location_(location) {
}

Ast::~Ast() {
}


AstExpression::AstExpression(FileLocation* location) :
    Ast(location) {
}

AstExpression::~AstExpression() {
}


AstStatement::AstStatement(FileLocation* location) :
    Ast(location) {
}

AstStatement::~AstStatement() {
}


AstLValue::AstLValue(FileLocation* location) :
    AstExpression(location) {
}

AstLValue::~AstLValue() {
}


AstType::AstType(FileLocation* location) :
    Ast(location) {
}

AstType::~AstType() {
}


AstClassType::AstClassType(FileLocation* location, string* identifier) :
    AstType(location), identifier_(identifier) {
    assert(identifier != NULL);
}

AstClassType::~AstClassType() {
}

void AstClassType::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    visitor.Leave(*this);
}


AstArrayType::AstArrayType(FileLocation* location, AstType* element_type) :
    AstType(location), element_type_(element_type) {
    assert(element_type != NULL);
}

AstArrayType::~AstArrayType() {
}

void AstArrayType::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    element_type().Accept(visitor);
    visitor.Leave(*this);
}


AstUnionType::AstUnionType(FileLocation* location, vector<AstType*>* types) :
    Ast(location), types_(types) {
    assert(types != NULL);
}

AstUnionType::~AstUnionType() {
}

void AstUnionType::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    for_each(types().begin(), types().end(), AcceptFunctor(visitor));
    visitor.Leave(*this);
}


AstVariable::AstVariable(FileLocation* location, string* identifier, 
                         AstUnionType* type) :
    Ast(location), identifier_(identifier), type_(type) {
    assert(identifier != NULL);
    assert(type != NULL);
}

AstVariable::~AstVariable() {
}

void AstVariable::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    type().Accept(visitor);
    visitor.Leave(*this);
}


AstMethod::AstMethod(FileLocation* location, string* identifier, 
                     vector<AstVariable*>* parameters, 
                     AstUnionType* return_type, AstStatement* body) :
    Ast(location), identifier_(identifier), parameters_(parameters), 
    return_type_(return_type), body_(body) {
    assert(identifier != NULL);
    assert(parameters != NULL);
    assert(body != NULL);
}

AstMethod::~AstMethod() {
}

void AstMethod::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    for_each(parameters().begin(), parameters().end(), AcceptFunctor(visitor));
    if (has_return_type()) {
        return_type().Accept(visitor);
    }
    body().Accept(visitor);
    visitor.Leave(*this);
}


AstClass::AstClass(FileLocation* location, string* identifier, 
                   AstClassType* superclass, vector<AstMethod*>* methods, 
                   vector<AstVariable*>* variables) :
    Ast(location), identifier_(identifier), superclass_(superclass), 
    methods_(methods), variables_(variables) {
    assert(identifier != NULL);
    assert(methods != NULL);
    assert(variables != NULL);
}

AstClass::~AstClass() {
}

void AstClass::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    if (has_superclass()) {
        superclass().Accept(visitor);
    }
    for_each(methods().begin(), methods().end(), AcceptFunctor(visitor));
    for_each(variables().begin(), variables().end(), AcceptFunctor(visitor));
    visitor.Leave(*this);
}


AstFile::AstFile(FileLocation* location, vector<AstClass*>* classes) :
    Ast(location), classes_(classes) {
    assert(classes != NULL);
}

AstFile::~AstFile() {
}

void AstFile::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    for_each(classes().begin(), classes().end(), AcceptFunctor(visitor));
    visitor.Leave(*this);
}


AstBlockStatement::AstBlockStatement(FileLocation* location, 
                                     vector<AstStatement*>* statements) :
    AstStatement(location), statements_(statements) {
    assert(statements != NULL);
}

AstBlockStatement::~AstBlockStatement() {
}

void AstBlockStatement::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    for_each(statements().begin(), statements().end(), AcceptFunctor(visitor));
    visitor.Leave(*this);
}


AstExpressionStatement::AstExpressionStatement(FileLocation* location, 
                                               AstExpression* expression) :
    AstStatement(location), expression_(expression) {
    assert(expression != NULL);
}

AstExpressionStatement::~AstExpressionStatement() {
}

void AstExpressionStatement::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    expression().Accept(visitor);
    visitor.Leave(*this);
}


AstAssignment::AstAssignment(FileLocation* location, AstLValue* left_side, 
                             AstExpression* right_side) :
    AstStatement(location), left_side_(left_side), right_side_(right_side) {
    assert(left_side != NULL);
    assert(right_side != NULL);
}

AstAssignment::~AstAssignment() {
}

void AstAssignment::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    left_side().Accept(visitor);
    right_side().Accept(visitor);
    visitor.Leave(*this);
}


AstVariableDeclaration::AstVariableDeclaration(FileLocation* location, 
                                               AstVariable* variable, 
                                               AstExpression* initial_value) :
    AstStatement(location), variable_(variable), initial_value_(initial_value) {
    assert(variable != NULL);
}

AstVariableDeclaration::~AstVariableDeclaration() {
}

void AstVariableDeclaration::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    variable().Accept(visitor);
    if (has_initial_value()) {
        initial_value().Accept(visitor);
    }
    visitor.Leave(*this);
}


AstEmptyStatement::AstEmptyStatement(FileLocation* location) :
    AstStatement(location) {
}

AstEmptyStatement::~AstEmptyStatement() {
}

void AstEmptyStatement::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    visitor.Leave(*this);
}


AstIf::AstIf(FileLocation* location, AstExpression* test, 
             AstStatement* true_path, AstStatement* false_path) :
    AstStatement(location), test_(test), true_path_(true_path), 
    false_path_(false_path) {
    assert(test != NULL);
    assert(true_path != NULL);
}

AstIf::~AstIf() {
}

void AstIf::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    test().Accept(visitor);
    true_path().Accept(visitor);
    if (has_false_path()) {
        false_path().Accept(visitor);
    }
    visitor.Leave(*this);
}


AstWhile::AstWhile(FileLocation* location, AstExpression* test, 
                   AstStatement* body) :
    AstStatement(location), test_(test), body_(body) {
    assert(test != NULL);
    assert(body != NULL);
}

AstWhile::~AstWhile() {
}

void AstWhile::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    test().Accept(visitor);
    body().Accept(visitor);
    visitor.Leave(*this);
}


AstFor::AstFor(FileLocation* location, AstStatement* initializer, 
               AstExpression* test, AstStatement* updater, AstStatement* body) :
    AstStatement(location), initializer_(initializer), test_(test), 
    updater_(updater), body_(body) {
    assert(initializer != NULL);
    assert(test != NULL);
    assert(updater != NULL);
    assert(body != NULL);
}

AstFor::~AstFor() {
}

void AstFor::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    initializer().Accept(visitor);
    test().Accept(visitor);
    updater().Accept(visitor);
    body().Accept(visitor);
    visitor.Leave(*this);
}


AstBreak::AstBreak(FileLocation* location) :
    AstStatement(location) {
}

AstBreak::~AstBreak() {
}

void AstBreak::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    visitor.Leave(*this);
}


AstReturn::AstReturn(FileLocation* location, AstExpression* expression) :
    AstStatement(location), expression_(expression) {
}

AstReturn::~AstReturn() {
}

void AstReturn::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    if (has_expression()) {
        expression().Accept(visitor);
    }
    visitor.Leave(*this);
}


AstParenthesizedExpression::AstParenthesizedExpression(FileLocation* location, 
                                                       AstExpression* expression) :
    AstExpression(location), expression_(expression) {
    assert(expression != NULL);
}

AstParenthesizedExpression::~AstParenthesizedExpression() {
}

void AstParenthesizedExpression::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    expression().Accept(visitor);
    visitor.Leave(*this);
}


AstAccess::AstAccess(FileLocation* location, AstExpression* base, 
                     string* identifier) :
    AstLValue(location), base_(base), identifier_(identifier) {
    assert(identifier != NULL);
}

AstAccess::~AstAccess() {
}

void AstAccess::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    if (has_base()) {
        base().Accept(visitor);
    }
    visitor.Leave(*this);
}


AstArrayAccess::AstArrayAccess(FileLocation* location, AstExpression* base, 
                               AstExpression* index) :
    AstLValue(location), base_(base), index_(index) {
    assert(base != NULL);
    assert(index != NULL);
}

AstArrayAccess::~AstArrayAccess() {
}

void AstArrayAccess::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    base().Accept(visitor);
    index().Accept(visitor);
    visitor.Leave(*this);
}


AstBinaryExpression::AstBinaryExpression(FileLocation* location, 
                                         AstExpression* left_side, 
                                         BinaryOperator op, 
                                         AstExpression* right_side) :
    AstExpression(location), left_side_(left_side), op_(op), 
    right_side_(right_side) {
    assert(left_side != NULL);
    assert(right_side != NULL);
}

AstBinaryExpression::~AstBinaryExpression() {
}

void AstBinaryExpression::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    left_side().Accept(visitor);
    right_side().Accept(visitor);
    visitor.Leave(*this);
}


AstUnaryExpression::AstUnaryExpression(FileLocation* location, 
                                       AstExpression* expression, 
                                       UnaryOperator op) :
    AstExpression(location), expression_(expression), op_(op) {
    assert(expression != NULL);
}

AstUnaryExpression::~AstUnaryExpression() {
}

void AstUnaryExpression::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    expression().Accept(visitor);
    visitor.Leave(*this);
}


AstCall::AstCall(FileLocation* location, AstAccess* base, 
                 vector<AstExpression*>* arguments) :
    AstExpression(location), base_(base), arguments_(arguments) {
    assert(base != NULL);
    assert(arguments != NULL);
}

AstCall::~AstCall() {
}

void AstCall::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    base().Accept(visitor);
    for_each(arguments().begin(), arguments().end(), AcceptFunctor(visitor));
    visitor.Leave(*this);
}


AstNew::AstNew(FileLocation* location) :
    AstExpression(location) {
}

AstNew::~AstNew() {
}


AstNewInstance::AstNewInstance(FileLocation* location, AstType* type, 
                               vector<AstExpression*>* arguments) :
    AstNew(location), type_(type), arguments_(arguments) {
    assert(type != NULL);
    assert(arguments != NULL);
}

AstNewInstance::~AstNewInstance() {
}

void AstNewInstance::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    type().Accept(visitor);
    for_each(arguments().begin(), arguments().end(), AcceptFunctor(visitor));
    visitor.Leave(*this);
}


AstNewArray::AstNewArray(FileLocation* location, AstType* element_type, 
                         AstExpression* size) :
    AstNew(location), element_type_(element_type), size_(size) {
    assert(element_type != NULL);
    assert(size != NULL);
}

AstNewArray::~AstNewArray() {
}

void AstNewArray::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    element_type().Accept(visitor);
    size().Accept(visitor);
    visitor.Leave(*this);
}


AstTypeTest::AstTypeTest(FileLocation* location, AstExpression* expression, 
                         AstType* type) :
    AstExpression(location), expression_(expression), type_(type) {
    assert(expression != NULL);
    assert(type != NULL);
}

AstTypeTest::~AstTypeTest() {
}

void AstTypeTest::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    expression().Accept(visitor);
    type().Accept(visitor);
    visitor.Leave(*this);
}


AstThis::AstThis(FileLocation* location) :
    AstExpression(location) {
}

AstThis::~AstThis() {
}

void AstThis::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    visitor.Leave(*this);
}


AstConstant::AstConstant(FileLocation* location) :
    AstExpression(location) {
}

AstConstant::~AstConstant() {
}


AstIntegerConstant::AstIntegerConstant(FileLocation* location, int value) :
    AstConstant(location), value_(value) {
}

AstIntegerConstant::~AstIntegerConstant() {
}

void AstIntegerConstant::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    visitor.Leave(*this);
}


AstStringConstant::AstStringConstant(FileLocation* location, string* value) :
    AstConstant(location), value_(value) {
    assert(value != NULL);
}

AstStringConstant::~AstStringConstant() {
}

void AstStringConstant::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    visitor.Leave(*this);
}


AstFloatConstant::AstFloatConstant(FileLocation* location, double value) :
    AstConstant(location), value_(value) {
}

AstFloatConstant::~AstFloatConstant() {
}

void AstFloatConstant::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    visitor.Leave(*this);
}


AstBooleanConstant::AstBooleanConstant(FileLocation* location, bool value) :
    AstConstant(location), value_(value) {
}

AstBooleanConstant::~AstBooleanConstant() {
}

void AstBooleanConstant::Accept(Visitor& visitor) const {
    visitor.Enter(*this);
    visitor.Leave(*this);
}


} // namespace indie
